package com.hjdb88.simplemvc.servlet;

import com.hjdb88.simplemvc.annotation.*;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebInitParam;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.SocketImpl;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @author hjdb88
 * @date 2020/10/17
 */
@WebServlet(name = "dispatcherServlet", urlPatterns = "/*", loadOnStartup = 1,
        initParams = {@WebInitParam(name = "basePackage", value = "com.hjdb88.simplemvc")})
public class DispatcherServlet extends HttpServlet {
    // 基包路径
    private String basePackage = "";
    // 基包下面所有的带包路径全限定类名
    private List<String> packageNames = new ArrayList<>();
    // 注解名称和实例化对象的映射关系
    private Map<String, Object> instanceMap = new HashMap<>();
    // 带包路径全限定类名和名称的映射关系
    private Map<String, String> nameMap = new HashMap<>();
    // URL地址和方法的映射关系
    private Map<String, Method> urlMap = new HashMap<>();
    // 方法和全限定类名的映射关系
    private Map<Method, String> methodMap = new HashMap<>();

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doPost(req, resp);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String uri = req.getRequestURI();
        String contextPath = req.getContextPath();
        String path = uri.replaceAll(contextPath, "");

        // 通过path查找方法
        Method method = urlMap.get(path);
        if (method != null) {
            // 通过方法拿到Controller对象
            String packageName = methodMap.get(method);
            String controllerName = nameMap.get(packageName);
            Object controller = instanceMap.get(controllerName);

            // 反射执行
            try {
                method.setAccessible(true);
                method.invoke(controller);
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (InvocationTargetException e) {
                e.printStackTrace();
            }
        }
    }

    @Override
    public void init(ServletConfig config) throws ServletException {
        basePackage = config.getInitParameter("basePackage");

        try {
            // 扫描基包获取所有带包路径全限定类名
            scanBasePackage(basePackage);
            // 把注解@Controller、@Service、@Repository的类实例化放入Map中，key为注解上的名称
            initInstance();
            // IOC注入
            initIoc();
            // 完成URL地址和方法的映射关系
            initUrlMap();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }

    private void initUrlMap() throws ClassNotFoundException {
        if (packageNames.size() < 1) {
            return;
        }

        for (String packageName : packageNames) {
            Class clazz = Class.forName(packageName);

            if (clazz.isAnnotationPresent(Controller.class)) {
                Method[] methods = clazz.getMethods();
                StringBuffer url = new StringBuffer();
                if (clazz.isAnnotationPresent(RequestMapping.class)) {
                    appendUrl(clazz, url);
                }

                for (Method method : methods) {
                    if (method.isAnnotationPresent(RequestMapping.class)) {
                        appendUrl(clazz, url);

                        urlMap.put(url.toString(), method);
                        methodMap.put(method, packageName);
                    }
                }
            }
        }
    }

    private void appendUrl(Class clazz, StringBuffer url) {
        RequestMapping requestMapping = (RequestMapping) clazz.getAnnotation(RequestMapping.class);
        if (!requestMapping.value().startsWith("/")) {
            url.append("/");
        }
        url.append(requestMapping.value());
    }

    private void initIoc() throws IllegalAccessException {
        for (Map.Entry<String, Object> entry : instanceMap.entrySet()) {
            Field[] fields = entry.getValue().getClass().getDeclaredFields();

            for (Field field : fields) {
                if (field.isAnnotationPresent(Qualifier.class)) {
                    String name = field.getAnnotation(Qualifier.class).value();
                    field.setAccessible(true);
                    field.set(entry.getValue(), instanceMap.get(name));
                }
            }
        }
    }

    private void initInstance() throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        if (packageNames.size() < 1) {
            return;
        }

        for (String packageName : packageNames) {
            Class clazz = Class.forName(packageName);

            if (clazz.isAnnotationPresent(Controller.class)) {
                Controller controller = (Controller) clazz.getAnnotation(Controller.class);
                String controllerName = controller.value();

                Constructor<? extends SocketImpl> constructor = clazz.getDeclaredConstructor();
                instanceMap.put(controllerName, constructor.newInstance());
                nameMap.put(packageName, controllerName);

                System.out.println("Controller: " + packageName + ", value: " + controllerName);
            } else if (clazz.isAnnotationPresent(Service.class)) {
                Service service = (Service) clazz.getAnnotation(Service.class);
                String serviceName = service.value();

                Constructor<? extends SocketImpl> constructor = clazz.getDeclaredConstructor();
                instanceMap.put(serviceName, constructor.newInstance());
                nameMap.put(packageName, serviceName);

                System.out.println("Service: " + packageName + ", value: " + serviceName);
            } else if (clazz.isAnnotationPresent(Repository.class)) {
                Repository repository = (Repository) clazz.getAnnotation(Repository.class);
                String repositoryName = repository.value();

                Constructor<? extends SocketImpl> constructor = clazz.getDeclaredConstructor();
                instanceMap.put(repositoryName, constructor.newInstance());
                nameMap.put(packageName, repositoryName);

                System.out.println("Repository: " + packageName + ", value: " + repositoryName);
            }
        }
    }

    private void scanBasePackage(String basePackage) {
        URL url = this.getClass().getClassLoader().getResource(basePackage.replaceAll("\\.", "/"));
        File basePackageFile = new File(url.getPath());
        System.out.println("scan: " + basePackageFile);
        File[] childFiles = basePackageFile.listFiles();
        for (File file : childFiles) {
            if (file.isDirectory()) {
                // 是目录则继续递归扫描
                scanBasePackage(basePackage + "." + file.getName());
            } else if (file.isFile()) {
                // 去除文件后缀名
                packageNames.add(basePackage + "." + file.getName().split("\\.")[0]);
            }
        }
    }
}
